#pragma once

namespace cr
{
	namespace Maths
	{
		static const float kPI		= (3.141592654f);
		static const float kTwoPI	= (2.0f*kPI);
		static const float kInvPI	= (1.0f/kPI);

		template <typename T>
		inline T Min(const T& a, const T& b)
		{
			return a < b ? a : b;
		}

		template <typename T>
		inline T Max(const T& a, const T& b)
		{
			return a > b ? a : b;
		}

		inline void SetViewLookAt( Matrix4f& outMatrix, Vec4fRef eye, Vec4fRef target, Vec4fRef up )
		{
			/*
			zaxis = normal(At - Eye)
			xaxis = normal(cross(Up, zaxis))
			yaxis = cross(zaxis, xaxis)

			xaxis.x           yaxis.x           zaxis.x          0
			xaxis.y           yaxis.y           zaxis.y          0
			xaxis.z           yaxis.z           zaxis.z          0
			-dot(xaxis, eye)  -dot(yaxis, eye)  -dot(zaxis, eye)  l
			*/

#if 1
			outMatrix = Matrix4f::Identity;
			outMatrix.m_Zaxis = ((target-eye).normalised3().getXYZ_W0());
			outMatrix.m_Xaxis = (up.cross3_W0(outMatrix.m_Zaxis).normalised3().getXYZ_W0());
			outMatrix.m_Yaxis = (outMatrix.m_Zaxis.cross3_W0(outMatrix.m_Xaxis));
			outMatrix.TransposeThis4x4();
 			outMatrix.m_translation = Vec4f(-outMatrix.m_Xaxis.dot3(eye),-outMatrix.m_Yaxis.dot3(eye),-outMatrix.m_Zaxis.dot3(eye),1.0f);
#else
			outMatrix = Matrix4f::Identity;
			outMatrix.m_Zaxis = (target - eye).normalised3().getXYZ_W0();
			outMatrix.m_Xaxis = up.cross3_W0(outMatrix.m_Zaxis).normalised3();
			outMatrix.m_Yaxis = outMatrix.m_Zaxis.cross3_W0(outMatrix.m_Xaxis).normalised3();
			outMatrix.m_translation = Vec4f(-outMatrix.m_Xaxis.dot3(eye),-outMatrix.m_Yaxis.dot3(eye),-outMatrix.m_Zaxis.dot3(eye),1.0f);
#endif
		}

		inline void SetPerspectiveProjection( Matrix4f& outMatrix, float fov, float aspectRatio, float nearZ, float farZ )
		{
			float yScale = (1.0f / tanf(fov * 0.5f));
			float xScale = yScale / aspectRatio;
			float zRangeInv = 1.0f / (farZ - nearZ);

			outMatrix.m_Xaxis			= Vec4f( xScale,	0.0f,   0.0f,					0.0f );
			outMatrix.m_Yaxis			= Vec4f( 0.0f,	yScale, 0.0f,					0.0f );
			outMatrix.m_Zaxis			= Vec4f( 0.0f,	0.0f,   farZ * zRangeInv,		1.0f );
			outMatrix.m_translation		= Vec4f( 0.0f,	0.0f,	-nearZ*farZ*zRangeInv,	0.0f );

			// 		xScale		0			0					0
			// 		0			yScale      0					0
			// 		0			0			zf/(zf-zn)			1
			// 		0			0			-zn*zf/(zf-zn)		0
			//		where:
			//		yScale = cot(fovY/2)
			//		xScale = yScale / aspect ratio
		}

		inline void SetPerspectiveProjectionLH(Matrix4f& outMatrix, float w, float h, float zn, float zf)
		{
			/*
			2*zn/w  0       0              0
			0       2*zn/h  0              0
			0       0       zf/(zf-zn)     1
			0       0       zn*zf/(zn-zf)  0
			*/

			outMatrix.m_Xaxis		= cr::Vec4f(2*zn/w,	0,			0,				0);
			outMatrix.m_Yaxis		= cr::Vec4f(0,		2*zn/h,		0,				0);
			outMatrix.m_Zaxis		= cr::Vec4f(0,		0,			zf/(zf-zn),		1);
			outMatrix.m_translation = cr::Vec4f(0,		0,			zn*zf/(zn-zf),	0);
		}

		inline void SetPerspectiveProjectionRH(Matrix4f& outMatrix, float w, float h, float zn, float zf)
		{
			/*
			2*zn/w  0       0              0
			0       2*zn/h  0              0
			0       0       zf/(zn-zf)    -1
			0       0       zn*zf/(zn-zf)  0
			*/
			outMatrix.m_Xaxis		= cr::Vec4f(2*zn/w,	0,			0,				0);
			outMatrix.m_Yaxis		= cr::Vec4f(0,		2*zn/h,		0,				0);
			outMatrix.m_Zaxis		= cr::Vec4f(0,		0,			zf/(zn-zf),		-1);
			outMatrix.m_translation = cr::Vec4f(0,		0,			zn*zf/(zn-zf),	0);
		}

		inline void SetPerspectiveProjectionFovLH(Matrix4f& outMatrix, float fov, float aspect, float zn, float zf)
		{
			/*
			xScale     0          0               0
			0        yScale       0               0
			0          0       zf/(zf-zn)         1
			0          0       -zn*zf/(zf-zn)     0
			where:
			yScale = cot(fovY/2)

			xScale = yScale / aspect ratio
			*/
			float yscale = (1.0f/tanf(fov*0.5f));
			float xscale = yscale / aspect;
			outMatrix.m_Xaxis		= cr::Vec4f(xscale,	0,			0,				0);
			outMatrix.m_Yaxis		= cr::Vec4f(0,		yscale,		0,				0);
			outMatrix.m_Zaxis		= cr::Vec4f(0,		0,			zf/(zf-zn),		1);
			outMatrix.m_translation = cr::Vec4f(0,		0,			-zn*zf/(zf-zn),	0);
		}

		inline void SetPerspectiveProjectionFovRH(Matrix4f& outMatrix, float fov, float aspect, float zn, float zf)
		{
			/*
			xScale       0            0              0
				0        yScale       0              0
				0        0        zf/(zn-zf)        -1
				0        0        zn*zf/(zn-zf)      0
			where:
			yScale = cot(fovY/2)

			xScale = yScale / aspect ratio
			*/
			float yscale = (1.0f/tanf(fov*0.5f));
			float xscale = yscale / aspect;
			outMatrix.m_Xaxis		= cr::Vec4f(xscale,	0,			0,				0);
			outMatrix.m_Yaxis		= cr::Vec4f(0,		yscale,		0,				0);
			outMatrix.m_Zaxis		= cr::Vec4f(0,		0,			zf/(zn-zf),		-1);
			outMatrix.m_translation = cr::Vec4f(0,		0,			zn*zf/(zn-zf),	0);
		}
	}
}
